package com.miyu.common.utils.redis;

import com.alibaba.fastjson.parser.ParserConfig;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.miyu.common.utils.redis.redisUtils.serializer.CustomFastJsonRedisSerializer;
import com.miyu.common.utils.redis.redisUtils.serializer.CustomStringRedisSerializer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.cache.interceptor.SimpleCacheErrorHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.*;

import java.time.Duration;

/**
 * 复述,缓存配置
 *redis 注解使用缓存
 * @author longm
 * @date 2022/10/08
 */
@Configuration
@Slf4j
public class RedisCacheConfig extends CachingConfigurerSupport {

    /**
     * 自定义生成redis-key
     *
     * @return
     */
    @Override
    @Bean
    public KeyGenerator keyGenerator() {
        return (o, method, objects) -> {
            StringBuilder sb = new StringBuilder();
            String me = o.getClass().getName();
            sb.append(me.substring(me.lastIndexOf(".")+1,me.length())).append(".");
            sb.append(method.getName());
            for (Object obj : objects) {
                if (null!=obj){
                    sb.append(obj.toString());
                }
            }
            return sb.toString();
        };
    }

    /**
     * 要启用spring缓存支持,需创建一个 CacheManager的 bean，CacheManager 接口有很多实现，这里Redis 的集成，用
     * RedisCacheManager这个实现类 Redis 不是应用的共享内存，它只是一个内存服务器，就像 MySql 似的，
     * 我们需要将应用连接到它并使用某种“语言”进行交互，因此我们还需要一个连接工厂以及一个 Spring 和 Redis 对话要用的
     * RedisTemplate， 这些都是 Redis 缓存所必需的配置，把它们都放在自定义的    中
     */
    @Bean()
    public CacheManager cacheManager( RedisConnectionFactory connectionFactory) {
        return new CacheRedisCacheManager(RedisCacheWriter.lockingRedisCacheWriter(connectionFactory), this.getRedisCacheConfigurationWithTtl(0));
    }

    private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
        // 设置 json 序列化
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);

        jackson2JsonRedisSerializer.setObjectMapper(om);

        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
        redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
                        RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())).
                // 设置过期时间
                        entryTtl(Duration.ofSeconds(seconds));
        return redisCacheConfiguration;
    }

    @Bean
    @Override
    public CacheErrorHandler errorHandler() {
        /* 用于捕获从Cache中进行CRUD时的异常的回调处理器。*/
        return new LoggingCacheErrorHandler();
    }



    /**
     * 缓存错误日志处理 缓存错误继续执行
     */
    static class LoggingCacheErrorHandler extends SimpleCacheErrorHandler {
        @Override
        public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
            log.error(String.format("cacheName:%s,cacheKey:%s", cache == null ? "unknown" : cache.getName(), key), exception);
            /* 不抛出错误，记录日志*/
            // super.handleCacheGetError(exception, cache, key);
        }

        @Override
        public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
            log.error(String.format("cacheName:%s,cacheKey:%s", cache == null ? "unknown" : cache.getName(), key), exception);
            //super.handleCachePutError(exception, cache, key, value);
        }

        @Override
        public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
            log.error(String.format("cacheName:%s,cacheKey:%s", cache == null ? "unknown" : cache.getName(), key), exception);
            // super.handleCacheEvictError(exception, cache, key);
        }

        @Override
        public void handleCacheClearError(RuntimeException exception, Cache cache) {
            log.error(String.format("cacheName:%s", cache == null ? "unknown" : cache.getName()), exception);
            // super.handleCacheClearError(exception, cache);
        }
    }
    /**
     *     1.项目启动时此方法先被注册成bean被spring管理,如果没有这个bean，则redis可视化工具中的中文内容（key或者value）都会以二进制存储，不易检查。
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(factory);

//        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//        ObjectMapper objectMapper = new ObjectMapper();
//        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
//        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
//        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        RedisSerializer stringRedisSerializer = null;
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = null;
        //若配置，则采用该方式
        jackson2JsonRedisSerializer = new CustomFastJsonRedisSerializer<>(Object.class);
        ParserConfig.getGlobalInstance().setAutoTypeSupport(false);
        stringRedisSerializer = new CustomStringRedisSerializer();


        /*// key采用String的序列化方式*/
        template.setKeySerializer(stringRedisSerializer);
        /* // hash的key也采用String的序列化方式*/
        template.setHashKeySerializer(stringRedisSerializer);
        /* // value序列化方式采用jackson*/
        template.setValueSerializer(jackson2JsonRedisSerializer);
        /* // hash的value序列化方式采用jackson*/
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }

    /**
     * 自定义redis序列化的机制,重新定义一个ObjectMapper.防止和MVC的冲突
     *
     * @return
     */

//    @Bean
//    public RedisSerializer<Object> redisSerializer() {
//
//        ObjectMapper objectMapper = new ObjectMapper();
//        //反序列化时候遇到不匹配的属性并不抛出异常
//        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//        //序列化时候遇到空对象不抛出异常
//        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
//        //反序列化的时候如果是无效子类型,不抛出异常
//        objectMapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false);
//        //不使用默认的dateTime进行序列化,
//        objectMapper.configure(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS, false);
//        //使用JSR310提供的序列化类,里面包含了大量的JDK8时间序列化类
//        objectMapper.registerModule(new JavaTimeModule());
//        //启用反序列化所需的类型信息,在属性中添加@class
//        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
//        //配置null值的序列化器
//        GenericJackson2JsonRedisSerializer.registerNullValueSerializer(objectMapper, null);
//        return new GenericJackson2JsonRedisSerializer(objectMapper);
//
//
//    }
//
//
//    @Bean
//    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory, RedisSerializer<Object> redisSerializer) {
//        RedisTemplate<Object, Object> template = new RedisTemplate<>();
//        template.setConnectionFactory(redisConnectionFactory);
//        template.setDefaultSerializer(redisSerializer);
//        template.setValueSerializer(redisSerializer);
//        template.setHashValueSerializer(redisSerializer);
//        template.setKeySerializer(StringRedisSerializer.UTF_8);
//        template.setHashKeySerializer(StringRedisSerializer.UTF_8);
//        template.afterPropertiesSet();
//        return template;
//    }

}
